
package gamepack;

import java.applet.Applet;
import java.util.Vector;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Color;
import java.util.Enumeration;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.IOException;

/**
 * "World" class encapsulates the game in which Sprites move.
 * A World consists of one or more rooms. (You may think of these
 * rooms anyway you want, like stages/levels/worlds etc)
 * The most basic responsiblity of this class is:		<br>
 * 1. Taking care of display of sprites/scores etc.		<br>
 * 2. Spawing/killing of sprites within/across rooms.	<br>
 *
 * If you want to write a World you must inherit from this class.
 * The World class is a Singleton.
 *.
 * @author Somanath
 * @version 1.1	 (last updated 21/10/02)
 */

/* Class Dependencies...Framelet (for Application size/Graphics object)
 *						Sprite (to manage/spawn/draw them)
 *                      Painter (requires for custom screen refreshing)
 *                      ThreadState (int threadState requires it)
 */


/* Core Minimum classes that must be written to test gamepack.
 * Prefferred order of development
 * 	Framelet 						(independent)
 *	CImage   						(independent)
 *  GamePackException				(independent)
 *  ResourceLoader					(CImage,Framelet,GamePackException)
 *
 *  Painter interface				(independent)
 *  Direction,interface				(independent)
 *  ThreadState interface 			(independent)
 *  Traversable interface			(independent)
 *  TimeStamp   					(independent)

 *  Sprite 	(CImage,Direction,ThreadState,GamePackException,Traversable)
 *  World (Framelet,Sprite,ThreadState,Painter,GamePackException)
 *
 *  Start testing by deriving from World and Sprite
 *
 */

public abstract class World implements Runnable {



	/** Reference to the current World object */
	public static World currentWorld;


	/** */


	//----------------------------------------------------------------------
	// attributes
	//----------------------------------------------------------------------
	/** The current room in the map.
		curroom is always initialized to zero when the World object is created.
		0 signifies the first room.	Every map should have atleast one room.

		Do not directly modify this variable, use the setRoom(int) method instead.
		This variable is kept public so that reads are faster.
	*/
	public Room curRoom = Room.getDefaultRoom();
	public String nextRoom = null;

	private Thread t;
	public int threadState;

	public boolean nextRoomSet() { return nextRoom == null ? false : true; }
	public void setNextRoom(String s) {  nextRoom = s; }
    public void changeToNextRoom() {
        // defensive check
        if ( nextRoom == null ) return;
        curRoom.exit();
        if( nextRoom.equals("default") ) {
            nextRoom = null;
            curRoom = Room.getDefaultRoom();
            return;
        }
        String curGameClass = currentWorld.getClass().getName();
        String packageName = curGameClass.substring( 0 , curGameClass.lastIndexOf(".")+1 );
        String nextRoomClassName = packageName + nextRoom;
        Game.println( nextRoomClassName );
        try {
              Class nextRoomClass = Class.forName( nextRoomClassName );
              nextRoom = null;    // reset nextRoomFlag
			  // Hmm. curRoom will get assigned only after the constuctor of room is called which means that sprites within the
			  // constructor will not able to access curRoom unless the base class Room constructor does curRoom = this;
              curRoom = (Room) newInstance( nextRoomClass );//nextRoomClass.newInstance(); 
        }
        catch( ClassNotFoundException ex) { throw new GamePackException("ClassNotFoundException @ changeToNextRoom()"); }
        catch( InstantiationException ex) { throw new GamePackException("InstantiationException @ changeToNextRoom()"); }
        catch( IllegalAccessException ex) { throw new GamePackException("IllegalAccessException @ changeToNextRoom()"); }
    }

    public Object newInstance(Class c) throws InstantiationException, IllegalAccessException  {
            return c.newInstance();
    }

	//----------------------------------------------------------------------
	// static methods
	//----------------------------------------------------------------------



	/** Returns an instance of the Singleton game class.( ie currently running game ) */
	public static World getInstance() {	return currentWorld;		}



	//----------------------------------------------------------------------
	// methods
	//----------------------------------------------------------------------


	/**
		Default Constructor. Some important initializations are <br>
		World.gameStatus = World.LOADING;					<br>
		curRoom = 0;
	*/

	public World() {
		Game.setState(Game.LOAD);
		threadState = ThreadState.RUN;
		//curRoom = 0;
	}


	/**
		This method must be compulsorily used as the last line of the
		constructor of classes derived from World and must not be used
		anywhere else.

		It makes invoking map the current map and starts its run method.
		The state is LOADING, when this method ends.
		@see currentWorld
	*/

	protected void startWorld() {
		if( currentWorld == null ) {
			currentWorld = this;
			t = new Thread(this);
			Game.setState( Game.PLAY );
			t.start();
		}
		else {
			currentWorld.setThreadState(ThreadState.STOP,Game.UNLOAD);
			new Thread( new Runnable() {
				public void run() {
					while(currentWorld != null);
					Game.setState( Game.LOAD );
					currentWorld = World.this;
					t = new Thread(this);
					t.start();
				}
			}).start();
		}
	}

	/**
	This method must be compulsorily used as the last line of the run()	method
	of classes derived from World and must not be used anywhere else. <br>

	World.state = UNKNOWN;										<br>
	It makes currentWorld = null;
	*/

	synchronized protected void stopWorld() {
		Game.setState(Game.UNLOAD);
		currentWorld = null;
		setThreadState(ThreadState.STOP,Game.UNLOAD);
	}



	public void interrupt() { t.interrupt(); }
	/** This method is provided only for rare situations. It must be used
		only on rare compelling occasions. This method return immediately.

		Changes the state of the thread associated with the World object.
		@param threadState may be 		<br>
			ThreadState.RUN 			<br>
			ThreadState.SUSPEND 		<br>
			ThreadState.STOP
		@param state may be UNKNOWN, LOADING, PLAYING, PAUSED, GAMEOVER
	*/
	protected synchronized void setThreadState(int threadState,int state) {
	    	Game.println(" World.setThreadState() invoked");
		if( this.threadState == threadState ) {
			Game.state = state;
			return;
		}
	    	Game.println(" World.setThreadState() before switch invoked");
		switch( threadState ) {
			// Note: Notification occurs after this method.
			case ThreadState.STOP:      notify(); break;
			case ThreadState.RUN:		notify(); break;
			case ThreadState.SUSPEND:   break;
		}
		this.threadState = threadState;
		Game.state = state;
	}

	/** Checks the state of the thread associated with the World object.
		@ returns false if the thread is dead, else returns true.
		If the thread was suspended then this method does not return till
		the thread gets into running state.
		*/

	protected synchronized boolean worldIsRunning() {
		while( threadState == ThreadState.SUSPEND ) {
			try { wait(); }
			catch(InterruptedException e) { }
		}
		return ( threadState == ThreadState.STOP ) ? false : true;
	}
}

